package com.example.im.core.service.impl;

import cn.hutool.core.date.DateUtil;
import com.corundumstudio.socketio.AckCallback;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.listener.ConnectListener;
import com.corundumstudio.socketio.listener.DisconnectListener;
import com.example.im.constant.Constant;
import com.example.im.constant.Constant.CLIENT_TYPE;
import com.example.im.core.listener.DialogueEventListener;
import com.example.im.core.service.SocketIOService;
import com.example.im.core.singleton.ClientInfoManager;
import com.example.im.core.singleton.UserInfoManager;
import com.example.im.core.vo.*;
import com.example.im.util.IPUtil;
import com.example.im.util.RedisUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * socketio启动类
 *
 */
@SuppressWarnings("unchecked")
@Service(value = "socketIOService")
public class SocketIOServiceImpl implements SocketIOService {

	private final Logger LOG = LoggerFactory.getLogger(SocketIOServiceImpl.class);
	
    @Autowired
    private SocketIOServer socketIOServer;
    @Autowired
    private RedisUtil redisUtil;

    //SocketIOClient单例管理类
    ClientInfoManager cim = ClientInfoManager.getInstance();
    //用户信息单例管理类
    UserInfoManager uim = UserInfoManager.getInstance();
    
    /**
     * 应用服务端口号
     */
    @Value("${server.port}")
    private String appServerPort;
    
    /**
     * 应用服务上下文
     */
    @Value("${server.context-path}")
    private String appServerContextPath;
    
    /**
     * Spring IoC容器创建之后，在加载SocketIOServiceImpl Bean之后启动
     * @throws Exception
     */
    @PostConstruct
    private void autoStartup() throws Exception {
        start();
    }

    /**
     * Spring IoC容器在销毁SocketIOServiceImpl Bean之前关闭,避免重启项目服务端口占用问题
     * @throws Exception
     */
    @PreDestroy
    private void autoStop() throws Exception  {
        stop();
    }
    
    public void start() {
        // 监听客户端连接
    	socketIOServer.addConnectListener(new ConnectListener() {
            @Override
            public void onConnect(SocketIOClient client) {
            	//获取连接参数
            	String token = getUrlParamsByClientAndKey(client, Constant.CONN_PARAM.TOKEN);
            	String userName = getUrlParamsByClientAndKey(client, Constant.CONN_PARAM.USERNAME);
            	String clientType = getUrlParamsByClientAndKey(client, Constant.CONN_PARAM.CLIENT_TYPE);

            	//连接前校验token
            	if(!"xxx".equals(token)) {
            		LOG.info("token不存在或者不正确，客户端{}连接不成功!", userName);
            		return;
            	}
            	
            	//缓存Socketio客户端
            	cacheClientInfo(client, userName, clientType);
            	
            	//缓存Socketio服务端映射到redis
            	cachesServerMapperToRedis(userName, clientType);
            	
            	LOG.info("客户端{}连接socketio成功，socketio服务所在IP：{}，连接时间：{}", userName, IPUtil.getHostIp(), DateUtil.format(new Date(), "yyyy/MM/dd HH:mm:ss"));
                
                //发送“连接socketio服务端”后的系统消息
//            	sendConnectAfterMsg(client, userName, clientType);
                
                //处理重新连接情况，比如：客户刷新页面，
            	//处理逻辑：根据客服接入关系(得到已接入的客服)，模拟客服发送消息给客户，为了避免客户刷新页面后找不到客服而导致发了不消息
//                handleReconnection(client, userName, clientType);
            }
			
        });

        // 监听客户端断开连接
    	socketIOServer.addDisconnectListener(new DisconnectListener() {
            @Override
            public void onDisconnect(SocketIOClient client) {
                String userName = getParamsByClient(client);
	            if (userName != null) {
	            	//删除本地缓存
	            	cim.removeClientInfo(userName);
	            	//断开连接，释放资源
	            	client.disconnect();
	            }
	            LOG.info("客户端{}断开，断开时间：{}!", userName, DateUtil.format(new Date(), "yyyy/MM/dd HH:mm:ss"));
            }
        });
    	
    	//对话事件监听器
    	DialogueEventListener dialogueEventListener = new DialogueEventListener();
    	socketIOServer.addEventListener(Constant.EVENT.DIALOGUE, ReceiveMsg.class, dialogueEventListener);
    	
//    	//系统事件监听器
//    	SystemEventListener systemEventListener = new SystemEventListener();
//    	socketIOServer.addEventListener(Constant.EVENT.SYSTEM, ReceiveMsg.class, systemEventListener);
//
//    	//关闭窗口事件监听器
//    	CloseWindowEventListener closeWindowEventListener = new CloseWindowEventListener();
//    	socketIOServer.addEventListener(Constant.EVENT.CLOSE_WINDOWS, ReceiveMsg.class, closeWindowEventListener);
    	
        //打开socketio服务
        socketIOServer.start();
    }


    public void stop() {
        if (socketIOServer != null) {
            socketIOServer.stop();
            socketIOServer = null;
        }
    }

    /**
     * 此方法为获取client连接中的参数，可根据需求更改
     * @param client
     * @return
     */
    private String getParamsByClient(SocketIOClient client) {
        // 从请求的连接中拿出参数（这里的userName必须是唯一标识）
        Map<String, List<String>> params = client.getHandshakeData().getUrlParams();
        List<String> list = params.get("userName");
        if (list != null && list.size() > 0) {
            return list.get(0);
        }
        return null;
    }


    /**
     * 根据key值获取通过Url方式传参的参数，即获取socketio连接时的参数
     * @param client
     * @param key
     * @return
     */
    private String getUrlParamsByClientAndKey(SocketIOClient client, String key) {
        // 从请求的连接中获取参数
        Map<String, List<String>> params = client.getHandshakeData().getUrlParams();
        List<String> list = params.get(key);
        if (list != null && list.size() > 0) {
            return list.get(0);
        }
        return null;
    }

    /**
     * 发送消息给客户端（含回调）
     * @param receiveMsg
     */
    private void sendMsgByCallback(ReceiveMsg receiveMsg, String event) {
    	String toUserName = receiveMsg.getData().getTo().getId();
    	String fromUserName = receiveMsg.getData().getFrom().getId();

    	ClientInfo toClientInfo = cim.getClientInfo(toUserName);

    	SendMsg sendMsg = new SendMsg();
    	sendMsg.setType(receiveMsg.getType());

    	SendMsgInfo sendMsgInfo = new SendMsgInfo();
    	sendMsgInfo.setMsgType(receiveMsg.getData().getMsgType());
    	sendMsgInfo.setMsgId(receiveMsg.getData().getMsgId());
    	sendMsgInfo.setSendTime(System.currentTimeMillis());
    	sendMsgInfo.setContent(receiveMsg.getData().getContent());

    	UserInfo from = new UserInfo();
    	UserInfo fromUserInfo = uim.getUserInfo(fromUserName);
    	if(fromUserInfo != null) {
    		from.setId(fromUserName);
        	from.setName(fromUserInfo.getName());
        	from.setAvatar(fromUserInfo.getAvatar());
    	}

    	UserInfo to = new UserInfo();
    	UserInfo toUserInfo = uim.getUserInfo(toUserName);
    	if(toUserInfo != null) {
    		to.setId(toUserName);
        	to.setName(toUserInfo.getName());
        	to.setAvatar(toUserInfo.getAvatar());
    	}

    	sendMsgInfo.setFrom(from);
    	sendMsgInfo.setTo(to);
    	sendMsg.setData(sendMsgInfo);

    	//注意：这里的2000，官方文档是秒，但经测试后好像是毫秒
    	toClientInfo.getSocketIOClient().sendEvent(event, new AckCallback<Object>(Object.class, 2000){
             @Override
             public void onSuccess(Object result) {
                 // 接收客户端返回的ack，意味着客户端已经接收到消息了
                System.out.println("ack from client: " + toClientInfo.getSocketIOClient().getSessionId() + " data: " + result);
             }
             @Override
             public void onTimeout() {
                 System.out.println("ACK超时");
             }

         }, sendMsg);

    }

//    /**
//     * 使用http方式转发消息至对应socketio服务器发送消息
//     * @param data
//     */
//    private void sendMsgUseHttp(ReceiveMsg data, String event) {
//    	ReceiveMsgByHttp receiveMsgByHttp = new ReceiveMsgByHttp();
//    	receiveMsgByHttp.setEvent(event);
//    	receiveMsgByHttp.setData(data);
//
//    	//获取对应的服务器host，例如：http://10.1.1.1:8888/demo-im-server
//    	Map<String, SocketIOServerMapper> mapper = redisUtil.get(Constant.CacheKey.SOCKETIO_SERVER_MAPPER, Map.class);
//		if(mapper == null) {
//			LOG.info("redis的SocketIO服务端与IP的映射关系为空，无法调用http转发消息接口");
//			return;
//		}
//		SocketIOServerMapper toServerMapper = mapper.get(data.getData().getTo().getId());
//		String appSrvHost = toServerMapper.getAppSrvHost();
//
//		LOG.info("消息准备使用http方式转发到{}此节点处理,节点对应的应用：{}", mapper.get(data.getData().getTo().getId()).getSocketioServerIp(), appSrvHost);
//
//		//http方法转发消息
//    	HttpInvoker.invoke(appSrvHost, ApiGroup.Api.SOCKETIO_SENDMSG, receiveMsgByHttp, new TypeReference<Result<ReceiveMsgByHttp>>(){}, false);
//    }

	 /**
     * 缓存Socketio客户端
     * @param client
     * @param userName
     * @param clientType
     */
	private void cacheClientInfo(SocketIOClient client, String userName, String clientType) {
		if (userName != null) {
    		ClientInfo clientInfo = new ClientInfo();
			clientInfo.setUserName(userName);
			clientInfo.setSocketIOClient(client);
			clientInfo.setConnectDate(new Date());
			clientInfo.setSocketioServerIp(IPUtil.getHostIp());

    		if(Constant.CLIENT_TYPE.CUSTOMER.equals(clientType)) {
    			clientInfo.setClientType(CLIENT_TYPE.CUSTOMER);
    		}else if(Constant.CLIENT_TYPE.CUSTOMER_SERVICE.equals(clientType)){
    			clientInfo.setClientType(CLIENT_TYPE.CUSTOMER_SERVICE);
    		}
    		cim.addOrUpdateClientInfo(userName, clientInfo);
    	}
	}

	/**
	 * 缓存Socketio服务端映射到redis
	 * @param userName
	 * @param clientType
	 */
	private void cachesServerMapperToRedis(String userName, String clientType) {
		if (userName != null) {
			String ip = IPUtil.getHostIp();
			SocketIOServerMapper socketIOServerMapper = new SocketIOServerMapper();
			socketIOServerMapper.setUserName(userName);
			socketIOServerMapper.setSocketioServerIp(ip);
			socketIOServerMapper.setAppSrvHost("http://"+ip+":"+appServerPort+appServerContextPath);
			socketIOServerMapper.setConnectDate(new Date());
    		if(Constant.CLIENT_TYPE.CUSTOMER.equals(clientType)) {
    			socketIOServerMapper.setClientType(CLIENT_TYPE.CUSTOMER);
    			socketIOServerMapper.setCustomerStatus(Constant.CustomerStatus.INIT);
    		}else if(Constant.CLIENT_TYPE.CUSTOMER_SERVICE.equals(clientType)){
    			socketIOServerMapper.setClientType(CLIENT_TYPE.CUSTOMER_SERVICE);
    		}

			//如果redisUtil.get(Constant.CacheKey.SOCKETIO_SERVER_MAPPER, Map.class)报错，则运行下面这行代码，刷新Constant.CacheKey.SOCKETIO_SERVER_MAPPER的value
//			redisUtil.set(Constant.CacheKey.SOCKETIO_SERVER_MAPPER, new HashMap<>());

    		Map<String, SocketIOServerMapper> mapper = redisUtil.get(Constant.CacheKey.SOCKETIO_SERVER_MAPPER, Map.class);
    		if(mapper == null) {
    			mapper = new HashMap<>();
    			mapper.put(userName, socketIOServerMapper);
        		redisUtil.set(Constant.CacheKey.SOCKETIO_SERVER_MAPPER, mapper);
    		}else {
    			// 不存在时才进缓存，存在则不需处理
    			if(!mapper.containsKey(userName)) {
    				mapper.put(userName, socketIOServerMapper);
            		redisUtil.set(Constant.CacheKey.SOCKETIO_SERVER_MAPPER, mapper);
    			}
    		}
    	}
	}

	@Override
	public void sendMsgSupportCluster(ReceiveMsg data, String event) {
    	//如果接收人socketioclient（即to对象）不在同一个节点无法以socketio方式发消息，
		//则查询redis的映射关系，得到接收人socketioclient所在IP，以http形式把消息转发到相应的节点处理
		ClientInfo clientInfo = cim.getClientInfo(data.getData().getTo().getId());
		if(clientInfo == null) {
			LOG.info("接收人{}的socketioclient不在同一个节点，则以http方式转发到相应节点处理，消息内容：{}", data.getData().getFrom().getId(), data.toString());
//			sendMsgUseHttp(data, event);
		}else {
			//如果接收人socketioclient（即to对象）在同一个节点，则使用socketio方式
			//发消息给对方(有回调)
			sendMsgByCallback(data, event);
		}
	}

}